#include "stdafx.h"
#include "stdlib/datetimelocal.hpp"
#include "stdlib/timezone.hpp"
#include <algorithm>
#include <ctime>
#include "stdlib/exception.hpp"
using namespace JHCPP::stdlib::jhException;

namespace JHCPP
{
	namespace stdlib
	{	
		CLocalDateTime::CLocalDateTime()
		{
			determineTzd(true);
		}

		CLocalDateTime::CLocalDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond)
		: m_dateTime(year, month, day, hour, minute, second, millisecond, microsecond)
		{
			determineTzd();
		}

		CLocalDateTime::CLocalDateTime(int tzd, int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond)
		:m_dateTime(year, month, day, hour, minute, second, millisecond, microsecond),
		m_tzd(tzd)
		{
		}

		CLocalDateTime::CLocalDateTime(double julianDay) : m_dateTime(julianDay)
		{
			determineTzd(true);
		}

		CLocalDateTime::CLocalDateTime(int tzd, double julianDay) : m_dateTime(julianDay), m_tzd(tzd)
		{
			adjustForTzd();
		}

		CLocalDateTime::CLocalDateTime(const CDateTime& dateTime) : m_dateTime(dateTime)
		{
			determineTzd(true);
		}

		CLocalDateTime::CLocalDateTime(int tzd, const CDateTime& dateTime) : m_dateTime(dateTime), m_tzd(tzd)
		{
			adjustForTzd();
		}

		CLocalDateTime::CLocalDateTime(int tzd, const CDateTime& dateTime, bool adjust) : m_dateTime(dateTime), m_tzd(tzd)
		{
			if (adjust)
				adjustForTzd();
		}

		CLocalDateTime::CLocalDateTime(const CLocalDateTime& dateTime) : m_dateTime(dateTime.m_dateTime), m_tzd(dateTime.m_tzd)
		{
		}

		CLocalDateTime::CLocalDateTime(CTimeStamp::UtcTimeVal utcTime, CTimeStamp::TimeDiff diff, int tzd)
		: m_dateTime(utcTime, diff),
		m_tzd(tzd)
		{
			adjustForTzd();
		}

		CLocalDateTime::~CLocalDateTime()
		{
		}

		CLocalDateTime& CLocalDateTime::operator = (const CLocalDateTime& dateTime)
		{
			if (&dateTime != this)
			{
				m_dateTime = dateTime.m_dateTime;
				m_tzd      = dateTime.m_tzd;
			}
			return *this;
		}

		CLocalDateTime& CLocalDateTime::operator = (const CTimeStamp& timestamp)
		{
			if (timestamp != this->timestamp())
			{
				m_dateTime = timestamp;
				determineTzd(true);
			}
			return *this;
		}

		CLocalDateTime& CLocalDateTime::operator = (double julianDay)
		{
			m_dateTime = julianDay;
			determineTzd(true);
			return *this;
		}

		CLocalDateTime& CLocalDateTime::assign(int year, int month, int day, int hour, int minute, int second, int millisecond, int microseconds)
		{
			m_dateTime.assign(year, month, day, hour, minute, second, millisecond, microseconds);
			determineTzd(false);
			return *this;
		}

		CLocalDateTime& CLocalDateTime::assign(int tzd, int year, int month, int day, int hour, int minute, int second, int millisecond, int microseconds)
		{
			m_dateTime.assign(year, month, day, hour, minute, second, millisecond, microseconds);
			m_tzd = tzd;
			return *this;
		}

		CLocalDateTime& CLocalDateTime::assign(int tzd, double julianDay)
		{
			m_tzd      = tzd;
			m_dateTime = julianDay;
			adjustForTzd();
			return *this;
		}

		void CLocalDateTime::swap(CLocalDateTime& dateTime)
		{
			m_dateTime.swap(dateTime.m_dateTime);
			std::swap(m_tzd, dateTime.m_tzd);
		}

		int CLocalDateTime::year() const
		{
			return m_dateTime.year();
		}

		int CLocalDateTime::month() const
		{
			return m_dateTime.month();
		}

		int CLocalDateTime::week(int firstDayOfWeek) const
		{
			return m_dateTime.week(firstDayOfWeek);
		}

		int CLocalDateTime::day() const
		{
			return m_dateTime.day();
		}

		int CLocalDateTime::dayOfWeek() const
		{
			return m_dateTime.dayOfWeek();
		}

		int CLocalDateTime::dayOfYear() const
		{
			return m_dateTime.dayOfYear();
		}

		int CLocalDateTime::hour() const
		{
			return m_dateTime.hour();
		}

		int CLocalDateTime::hourAMPM() const
		{
			return m_dateTime.hourAMPM();
		}

		bool CLocalDateTime::isAM() const
		{
			return m_dateTime.isAM();
		}

		bool CLocalDateTime::isPM() const
		{
			return m_dateTime.isPM();
		}

		int CLocalDateTime::minute() const
		{
			return m_dateTime.minute();
		}

		int CLocalDateTime::second() const
		{
			return m_dateTime.second();
		}

		int CLocalDateTime::millisecond() const
		{
			return m_dateTime.millisecond();
		}

		int CLocalDateTime::microsecond() const
		{
			return m_dateTime.microsecond();
		}

		double CLocalDateTime::julianDay() const
		{
			return m_dateTime.julianDay();
		}

		int CLocalDateTime::tzd() const
		{
			return m_tzd;
		}

		CDateTime CLocalDateTime::utc() const
		{
			return CDateTime(m_dateTime.utcTime(), -((CTimeStamp::TimeDiff) m_tzd)*CTimeSpan::SECONDS);
		}

		CTimeStamp CLocalDateTime::timestamp() const
		{
			return CTimeStamp::fromUtcTime(m_dateTime.utcTime());
		}

		CTimeStamp::UtcTimeVal CLocalDateTime::utcTime() const
		{
			return m_dateTime.utcTime() - ((CTimeStamp::TimeDiff) m_tzd)*10000000;
		}

		bool CLocalDateTime::operator == (const CLocalDateTime& dateTime) const
		{
			return utcTime() == dateTime.utcTime();
		}

		bool CLocalDateTime::operator != (const CLocalDateTime& dateTime) const	
		{
			return utcTime() != dateTime.utcTime();
		}

		bool CLocalDateTime::operator <  (const CLocalDateTime& dateTime) const	
		{
			return utcTime() < dateTime.utcTime();
		}

		bool CLocalDateTime::operator <= (const CLocalDateTime& dateTime) const	
		{
			return utcTime() <= dateTime.utcTime();
		}

		bool CLocalDateTime::operator >  (const CLocalDateTime& dateTime) const	
		{
			return utcTime() > dateTime.utcTime();
		}

		bool CLocalDateTime::operator >= (const CLocalDateTime& dateTime) const	
		{
			return utcTime() >= dateTime.utcTime();
		}

		CLocalDateTime CLocalDateTime::operator + (const CTimeSpan& span) const
		{
			CDateTime tmp(utcTime(), span.totalMicroseconds());
			return CLocalDateTime(tmp);
		}

		CLocalDateTime CLocalDateTime::operator - (const CTimeSpan& span) const
		{
			CDateTime tmp(utcTime(), -span.totalMicroseconds());
			return CLocalDateTime(tmp);
		}

		CTimeSpan CLocalDateTime::operator - (const CLocalDateTime& dateTime) const
		{
			return CTimeSpan((utcTime() - dateTime.utcTime())/10);
		}

		CLocalDateTime& CLocalDateTime::operator += (const CTimeSpan& span)
		{
			*this = CDateTime(utcTime(), span.totalMicroseconds());
			return *this;
		}

		CLocalDateTime& CLocalDateTime::operator -= (const CTimeSpan& span)
		{
			*this = CDateTime(utcTime(), -span.totalMicroseconds());
			return *this;
		}

		void CLocalDateTime::determineTzd(bool adjust)
		{
			if (adjust)
			{
				std::time_t epochTime = m_dateTime.timestamp().epochTime();
			#if defined(_WIN32) 
				std::tm* broken = std::localtime(&epochTime);

				if (!broken) throw SystemException("cannot get local time");
				m_tzd = (CTimeZone::utcOffset() + ((broken->tm_isdst == 1) ? 3600 : 0));
			#else
				std::tm broken;


				if (!localtime_r(&epochTime, &broken))
					throw SystemException("cannot get local time");
				m_tzd = (CTimeZone::utcOffset() + ((broken.tm_isdst == 1) ? 3600 : 0));
			#endif
				adjustForTzd();
			}
			else
			{
				int dst;
				dstOffset(dst);
				m_tzd = (CTimeZone::utcOffset() + dst);
			}
		}

		void CLocalDateTime::adjustForTzd()
		{
			m_dateTime += CTimeSpan(((CTimeStamp::TimeDiff) m_tzd)*CTimeSpan::SECONDS);
		}

		std::time_t CLocalDateTime::dstOffset(int& dstOffset) const
		{
			std::time_t local;
			std::tm     broken;

			broken.tm_year  = (m_dateTime.year() - 1900);
			broken.tm_mon   = (m_dateTime.month() - 1);
			broken.tm_mday  = m_dateTime.day();
			broken.tm_hour  = m_dateTime.hour();
			broken.tm_min   = m_dateTime.minute();
			broken.tm_sec   = m_dateTime.second();
			broken.tm_isdst = -1;

			local = std::mktime(&broken);

			dstOffset = (broken.tm_isdst == 1) ? 3600 : 0;
			return local;
		}
	}//end of namespace stdlib
}//end of namespace JHCPP
